Skip to content

fix(iam,organizations,core): gate cross-account AssumeRoot on org membership (bug-hunt 5.2)#2292

Merged
vieiralucas merged 1 commit into
mainfrom
bh-root
Jul 16, 2026
Merged

fix(iam,organizations,core): gate cross-account AssumeRoot on org membership (bug-hunt 5.2)#2292
vieiralucas merged 1 commit into
mainfrom
bh-root

Conversation

@vieiralucas

@vieiralucas vieiralucas commented Jul 16, 2026

Copy link
Copy Markdown
Member

Summary

sts:AssumeRoot into another account was gated only by the caller account's own organizations_root_sessions flag. Any account that enabled RootSessions could therefore mint :root credentials for any account — inside or outside its organization — with no management-account or membership check. (bug-hunt 5.2, MED cross-account escalation.)

Cross-account AssumeRoot now additionally requires the organization topology to authorize it:

  • the target must be a member of the caller's organization, and
  • the caller must be that org's management account (or a registered delegated administrator for centralized root access).

Same-account AssumeRoot is unchanged (always allowed — the recorded conformance baseline sts_assume_root targets the caller's own account).

Design

  • core (auth.rs): new OrgMembershipResolver trait — can_assume_root_into(caller, target). Mirrors the existing ScpResolver boundary so IAM/STS need no direct dependency on the organizations crate.
  • organizations (resolver.rs): OrganizationsMembershipResolver implements it over the shared org state — target must be enrolled, caller must be management or a delegated admin for iam.amazonaws.com.
  • iam (StsService): new with_org_membership(...) builder; the cross-account branch consults the resolver. With no resolver wired (single-account setups / unit tests), cross-account AssumeRoot is denied — there is no org topology to authorize it.
  • server (main.rs): wires the resolver from the live organizations state.

Test plan

  • cargo test -p fakecloud-organizations --lib membership_resolver — management→member allowed; non-member target, non-management caller, and no-org all denied; delegated admin allowed.
  • cargo test -p fakecloud-iam --lib assume_root — cross-account allowed via a mock resolver; denied when the org disallows; denied with no resolver; same-account still allowed.
  • cargo build --workspace + cargo build --bin fakecloud clean (main.rs wiring).
  • cargo clippy -p fakecloud-core -p fakecloud-organizations -p fakecloud-iam --tests -- -D warnings clean.

No API surface / SDK / docs change (authorization behavior only; the operation shape is unchanged).


Summary by cubic

Fixes a cross-account escalation in sts:AssumeRoot. Cross-account AssumeRoot now requires the target to be in the caller’s org and the caller to be the management account or a delegated admin; same-account is unchanged.

  • Bug Fixes

    • Added OrgMembershipResolver in fakecloud-core; implemented OrganizationsMembershipResolver in fakecloud-organizations.
    • Updated StsService to consult the resolver and deny cross-account AssumeRoot when not authorized or when no resolver is wired; added focused tests.
    • Wired the resolver in fakecloud-server.
  • Migration

    • If you construct StsService outside our server, call with_org_membership(...) to enable authorized cross-account AssumeRoot; without it, cross-account is denied.

Written for commit 14e6e38. Summary will update on new commits.

Review in cubic

…bership (bug-hunt 5.2)

`sts:AssumeRoot` into another account was gated only by the caller account's own
`organizations_root_sessions` flag, so any account that enabled RootSessions
could mint `:root` credentials for ANY account — in or out of its organization —
with no management-account or membership check.

Cross-account AssumeRoot now additionally requires that the organization
topology authorize it: the target must be a member of the caller's organization
AND the caller must be that org's management account (or a registered delegated
administrator for centralized root access). Same-account AssumeRoot is
unchanged (always allowed, per the recorded conformance baseline).

- core: new `OrgMembershipResolver` trait (`can_assume_root_into`).
- organizations: `OrganizationsMembershipResolver` implements it over the shared
  org state (management-account / delegated-admin + member-enrollment checks).
- iam: `StsService::with_org_membership`; the cross-account branch consults the
  resolver. With no resolver wired (single-account setups), cross-account
  AssumeRoot is denied — there is no org topology to authorize it.
- server: wire the resolver from the live organizations state.

Tests: organizations resolver (management/member/non-member/non-management/
delegated-admin/no-org); STS assume_root (cross-account allowed via a mock
resolver, denied when the org disallows, denied with no resolver). Conformance
`sts_assume_root` is same-account and unaffected.
@vieiralucas vieiralucas requested a review from Copilot July 16, 2026 01:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@vieiralucas vieiralucas merged commit ae46d7e into main Jul 16, 2026
155 checks passed
@vieiralucas vieiralucas deleted the bh-root branch July 16, 2026 04:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants